/*
 * Copyright (c) 2018-2024 北京华恒盛世科技有限公司
 * QUICKPOOL is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *     http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
*/

#include "subcmd.h"

static json *usergroup_2json(struct QuickPoolUserGroupInfo *);
static json *hostgroup_2json(struct QuickPoolHostGroupInfo *, int);
static void listgroup_usage(const char *);

/**
 * @brief       qp node/user group handler, to query node group information.
 *
 * @param[in]   argc          #1: number of input argument
 * @param[in]   argv          #2: string array of the input argument
 *
 * @retval      0             #1: succeed
 * @retval      1             #2: input option is wrong or kind of input argument not specified
 * @retval      3             #3: failed to query node/user group information
 * @retval      4             #4: not enough memory to print json string
 *
 */
int
cmd_list_groups(int argc, char **argv)
{
    const char *options;
    static struct option long_options[4];

    long_options[0].name = "help";
    long_options[0].has_arg = no_argument;
    long_options[0].flag = 0;
    long_options[0].val = 'h';

    long_options[1].name = "recursive";
    long_options[1].has_arg = no_argument;
    long_options[1].flag = 0;
    long_options[1].val = 'r';

    if (0 == strcmp(argv[0], "usergroup")) {
        options = "hr";

        long_options[2].name = NULL;
        long_options[2].has_arg = no_argument;
        long_options[2].flag = 0;
        long_options[2].val = 0;
    } else {
        options = "hrs";

        long_options[2].name = "slot";
        long_options[2].has_arg = no_argument;
        long_options[2].flag = 0;
        long_options[2].val = 's';

        long_options[3].name = NULL;
        long_options[3].has_arg = no_argument;
        long_options[3].flag = 0;
        long_options[3].val = 0;
    }

    int slots = 0;
    unsigned int reqOption = GRP_ALL;

    while (TRUE) {

        int option_index = 0;
        int opt;
        opt = getopt_long(argc, argv, options, long_options, &option_index);
        if (-1 == opt) {
            break;
        }

        switch (opt) {
	case 'r':
            reqOption |= GRP_RECURSIVE;
            break;
	case 's':
            ++slots;
            break;
        case 'h':
            listgroup_usage(argv[0]);
            exit(0);
        case '?':
        default:
            listgroup_usage(argv[0]);
            exit(1);
        }
    }

    // if not specify a group name, set all to TRUE
    int all = FALSE;
    unsigned int numGroups = 0;
    char **groups=NULL, **groupPoint=NULL;
    numGroups = get_argument_names(argc, argv, optind, &groups, &all, "group");
    if (0 < numGroups && !all) {
        reqOption &= ~GRP_ALL;
        groupPoint = groups;
    }

    struct request groupReq;
    memset(&groupReq, 0, sizeof(struct request));

    if (0 == numGroups || NULL == groupPoint) {

        // to query all the node/user group in the cluster.
        groupReq.number = 2;
        groupReq.keyvalues = (struct keyvalue *)calloc(2, sizeof(struct keyvalue));
        if (NULL == groupReq.keyvalues) {
            fprintf(stderr, "calloc(%ld) failed due to %m.\n", 2*sizeof(struct keyvalue));
            exit(4);
        }

        groupReq.keyvalues[0].key = REQUEST_KEY_NUMBER;
        groupReq.keyvalues[0].value = (char *)calloc(12, sizeof(char));
        if (NULL == groupReq.keyvalues[0].value) {
            fprintf(stderr, "calloc(%d) failed due to %m.\n", 12);
            exit(4);
        }
        snprintf(groupReq.keyvalues[0].value, 12, "%d", 0);

        groupReq.keyvalues[1].key = REQUEST_KEY_OPTIONS;
        groupReq.keyvalues[1].value = (char *)calloc(12, sizeof(char));
        if (NULL == groupReq.keyvalues[1].value) {
            fprintf(stderr, "calloc(%d) failed due to %m.\n", 12);
            exit(4);
        }
    } else {

        // to query specific node/user group.
        groupReq.number = 3;
        groupReq.keyvalues = (struct keyvalue *)calloc(3, sizeof(struct keyvalue));
        if (NULL == groupReq.keyvalues) {
            fprintf(stderr, "calloc(%ld) failed due to %m.\n", 3*sizeof(struct keyvalue));
            exit(4);
        }

        groupReq.keyvalues[0].key = REQUEST_KEY_NUMBER;
        groupReq.keyvalues[0].value = (char *)calloc(12, sizeof(char));
        if (NULL == groupReq.keyvalues[0].value) {
            fprintf(stderr, "calloc(%d) failed due to %m.\n", 12);
            exit(4);
        }
        snprintf(groupReq.keyvalues[0].value, 12, "%d", numGroups);

        groupReq.keyvalues[1].key = REQUEST_KEY_OPTIONS;
        groupReq.keyvalues[1].value = (char *)calloc(12, sizeof(char));
        if (NULL == groupReq.keyvalues[1].value) {
            fprintf(stderr, "calloc(%d) failed due to %m.\n", 12);
            exit(4);
        }

        groupReq.keyvalues[2].key = REQUEST_KEY_NAMES;
        unsigned int size=0, i;
        for (i = 0; i < numGroups; i++) {
            size += strlen(groupPoint[i]) + 1;
        }
        groupReq.keyvalues[2].value = (char *)calloc(size, sizeof(char));
        if (NULL == groupReq.keyvalues[2].value) {
            fprintf(stderr, "calloc(%d) failed due to %m.\n", size);
            exit(4);
        }

        for (i = 0; i < numGroups; i++) {
			
            if (i == 0) {
                strcpy(groupReq.keyvalues[2].value, groupPoint[i]);
            } else {
                strcat(groupReq.keyvalues[2].value, " ");
                strcat(groupReq.keyvalues[2].value, groupPoint[i]);
            }
        }
    }
    FREEUP(groups);

    struct QuickPoolUserGroupReply *usergroupReply=NULL;
    struct QuickPoolHostGroupReply *hostgroupReply=NULL;

    if (NULL != strstr(argv[0], "usergroup")) {

        reqOption |= USER_GRP;
        snprintf(groupReq.keyvalues[1].value, 12, "%d", reqOption);

        usergroupReply = client_get_usergroupinfo(&groupReq, NULL);
        if (quickpool_errno != 0 || NULL == usergroupReply) {

            if (NULL != usergroupReply) {
                // JS replied error message.
                fprintf(stderr, "%s\n", usergroupReply->error_message);

                gf_xdr_free(libxdr_usergroup_reply, usergroupReply);
                FREEUP(usergroupReply);
            } else {
                fprintf(stderr, "Fail to query user group due to above error.\n");
            }

            gf_xdr_free(gf_xdr_request, &groupReq);

            exit(3);
        }
    } else if (NULL != strstr(argv[0], "hostgroup")) {

	reqOption |= HOST_GRP;
        snprintf(groupReq.keyvalues[1].value, 12, "%d", reqOption);

        hostgroupReply = client_get_hostgroupinfo(&groupReq, NULL);
        if (quickpool_errno != 0 || NULL == hostgroupReply) {

            if (NULL != hostgroupReply) {
                // JS replied error message.
                fprintf(stderr, "%s\n", hostgroupReply->error_message);

                gf_xdr_free(libxdr_hostgroup_reply, hostgroupReply);
                FREEUP(hostgroupReply);
            } else {
                fprintf(stderr, "Fail to query user group due to above error.\n");
            }

            gf_xdr_free(gf_xdr_request, &groupReq);

            exit(3);
        }
    }

    gf_xdr_free(gf_xdr_request, &groupReq);

    // print out node/user group information in json format.
    json *jsonGroupList = json_CreateObject();
    unsigned int i;
    if (reqOption & USER_GRP) {
        for (i = 0; i < usergroupReply->num_groups; i++) {

            json *jsonUserGroup =  usergroup_2json(&(usergroupReply->groups[i]));
            json_AddItemToObject(jsonGroupList, usergroupReply->groups[i].group_name, jsonUserGroup);
        }
    } else if (reqOption & HOST_GRP) {
        for (i = 0; i < hostgroupReply->num_groups; i++) {

            json *jsonHostGroup =  hostgroup_2json(&(hostgroupReply->groups[i]), slots);
            json_AddItemToObject(jsonGroupList, hostgroupReply->groups[i].group_name, jsonHostGroup);
        }
    }

    if (0 < json_GetArraySize(jsonGroupList)) {
        char *strJson;
        strJson = json_Print(jsonGroupList);
        if (NULL != strJson) {
            fprintf(stdout, "%s\n", strJson);
            FREEUP(strJson);
        } else {
            perror("malloc");
            json_Delete(jsonGroupList);
            exit(4);
        }
    } else {
        // nothing in json means there's no user/node group in the cluster.
        fprintf(stderr, "No user/node group information in the cluster.\n");
    }

    if (reqOption & USER_GRP) {
        gf_xdr_free(libxdr_usergroup_reply, usergroupReply);
        FREEUP(usergroupReply);
    } else {
        gf_xdr_free(libxdr_hostgroup_reply, hostgroupReply);
        FREEUP(hostgroupReply);
    }
    json_Delete(jsonGroupList);

    exit(0);
} // end function cmd_list_groups

/* ------------------------------------------------
 *
 *  below are static functions used in this file
 *
 * ------------------------------------------------ */

/**
 * @brief       transform user group structure to json.
 *
 * @param[in]   group         #1: user group to be tranformed
 *
 * @retval      not NULL      #1: transformed json object
 *
 */
static json *
usergroup_2json(struct QuickPoolUserGroupInfo *group)
{
    json *jsonUserGroup = json_CreateObject();

    s2j_json_set_basic_element(jsonUserGroup, group, string, "GROUP_NAME", group_name);

    char *strUsers;
    strUsers = namelist_2string(group->member_list);
    json_AddStringToObject(jsonUserGroup, "USERS", strUsers);

    if (NULL != group->user_shares && 0 < strlen(group->user_shares)) {
        s2j_json_set_basic_element(jsonUserGroup, group, string, "USER_SHARES", user_shares);
    }

    if (NULL != group->exclude_members && 0 < strlen(group->exclude_members)) {
        s2j_json_set_basic_element(jsonUserGroup, group, string, "EXCLUDE_USERS", exclude_members);
    }

    FREEUP(strUsers);

    return jsonUserGroup;
} // end function usergroup_2json

/**
 * @brief       transform node group structure to json.
 *
 * @param[in]   group         #1: node group to be tranformed
 * @param[in]   slots         #2: maximum slots of the node group
 *
 * @retval      not NULL      #1: transformed json object
 *
 */
static json *
hostgroup_2json(struct QuickPoolHostGroupInfo *group, int slots)
{
    json *jsonHostGroup = json_CreateObject();

    s2j_json_set_basic_element(jsonHostGroup, group, string, "GROUP_NAME", group_name);

    char *strNodes;
    strNodes = namelist_2string(group->member_list);
    json_AddStringToObject(jsonHostGroup, "HOSTS", strNodes);

    if (0 < slots) {
        s2j_json_set_basic_element(jsonHostGroup, group, int, "MAX_SLOTS", max_slots);
    }

    if (NULL != group->exclude_members && 0 < strlen(group->exclude_members)) {
        s2j_json_set_basic_element(jsonHostGroup, group, string, "EXCLUDE_USERS", exclude_members);
    }

    FREEUP(strNodes);

    return jsonHostGroup;
} // end function hostgroup_2json

static void
listgroup_usage(const char *cmd)
{
    if (strstr(cmd, "usergroup") != NULL) {
        fprintf(stdout, "qp user group list usage: [-h, --help] [-r, --recursive] [group_name ...]\n");
    } else {
        fprintf(stdout, "qp host group list usage: [-h, --help] [-r, --recursive] [-s] [group_name ...]\n");
    }

    return;
}
